﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Timers;

namespace Samurai.Wakizashi.Caching
{
    /// <summary>
    /// Interface to dictate members of a simple cache used to 
    /// hold a collection of cached items of the same type.
    /// </summary>
    /// <typeparam name="T">The type being held in this cache instance.</typeparam>
    public interface ICacheStore<T>
    {
        /// <summary>
        /// Caches the provided item.
        /// </summary>
        /// <param name="item">The item to store in the cache.</param>
        void CacheItem(T item);

        /// <summary>
        /// Evicts an item from the cache. Performs a simple reference check 
        /// to match the item.
        /// </summary>
        /// <param name="item">The item.</param>
        void EvictItem(T item);

        /// <summary>
        /// Clears all items from the cache.
        /// </summary>
        void Clear();

        /// <summary>
        /// Gets all items held in the cache.
        /// </summary>
        /// <value>All items in the cache.</value>
        IEnumerable<T> AllItems { get; }
    }

    /// <summary>
    /// Store of single-type items.
    /// </summary>
    /// <remarks>Items in this cache never expire and are never automatically 
    /// evicted from the cache. External management of items is necessary.</remarks>
    /// <typeparam name="T">The type of items to store in the cache.</typeparam>
    public class CacheStore<T> : DisposableBase, ICacheStore<T> where T : class
    {
        protected readonly ConcurrentBag<T> Items;

        /// <summary>
        /// Initializes a new instance of the <see cref="CacheStore&lt;T&gt;"/> class.
        /// </summary>
        public CacheStore()
        {
            Items = new ConcurrentBag<T>();
        }

        #region Implementation of ICacheStore<T>

        /// <summary>
        /// Caches the provided item. If a reference to the item already 
        /// exists in the cache, no change occurs.
        /// </summary>
        /// <param name="item">The item to store in the cache.</param>
        /// <exception cref="ArgumentNullException">Throws if <paramref name="item"/> parameter is null.</exception>
        public virtual void CacheItem(T item)
        {
            if (item == null) throw new ArgumentNullException("item");

            if (FindItemByEquality(item) == null)
                Items.Add(item);
        }

        /// <summary>
        /// Evicts an item from the cache. Performs a matching check 
        /// using the Equals method on the base type, <see cref="Object"/>.
        /// </summary>
        /// <param name="item">The item to evict from the cache.</param>
        /// <exception cref="ArgumentNullException">Throws if <paramref name="item"/> parameter is null.</exception>
        public virtual void EvictItem(T item)
        {
            if (item == null) throw new ArgumentNullException("item");

            if (FindItemByEquality(item) != null)
                Items.TryTake(out item);
        }

        /// <summary>
        /// Clears all items from the cache.
        /// </summary>
        public void Clear()
        {
            var allItems = Items.ToArray();
            foreach (var item in allItems)
            {
                var i = item;
                Items.TryTake(out i);
            }
        }

        /// <summary>
        /// Gets all items held in the cache.
        /// </summary>
        /// <value>All items in the cache.</value>
        public virtual IEnumerable<T> AllItems
        {
            get
            {
                return Items.ToArray();
            }
        }

        #endregion

        private T FindItemByEquality(T itemToMatch)
        {
            return Items.SingleOrDefault(i => i.Equals(itemToMatch));
        }
    }

    public interface IKeyedCacheStore<in TKey, T> where T : class
    {
        /// <summary>
        /// Caches the provided item an associates it with the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="item">The item.</param>
        void CacheItem(TKey key, T item);

        /// <summary>
        /// Evicts the item from the cache.
        /// </summary>
        /// <param name="key">The key.</param>
        void EvictItem(TKey key);

        /// <summary>
        /// Clears all items from the cache.
        /// </summary>
        void Clear();

        /// <summary>
        /// Gets the item located at the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>The item requested or <c>null</c> if none is found.</returns>
        T GetItem(TKey key);

        /// <summary>
        /// Gets the item at the specified key and attempts to cast it to the specified type.
        /// </summary>
        /// <typeparam name="TCast">The type of the cast.</typeparam>
        /// <param name="key">The key.</param>
        /// <returns>The requested item. If none is found, a <c>null</c> reference is returned.</returns>
        TCast GetItemAs<TCast>(TKey key);

        /// <summary>
        /// Gets all items stored in the cache.
        /// </summary>
        /// <value>All items.</value>
        IEnumerable<T> AllItems { get; }
    }
}
